%{


    #include <QtDebug>
    #include <QList>
    #include <FunTable.h>
    #include <UserFunTable.h>
    #include <ConstantsTable.h>
    #include <BinaryOpNode.h>
    #include <UnaryOpNode.h>
    #include <VariableNode.h>
    #include <ValueNode.h>
    #include <MathConstantNode.h>
    #include <QStringList>
    #include <ParserException.h>
    #define YYDEBUG 0

    extern int yylex (void);
    extern void yyerror (char const *);
    extern int yyparse();
    extern void yyset_in(FILE*);
    extern int yy_scan_string(const char*);

    /*Distruge listPtr si il initializeaza cu 0*/
    void clear(QList<VariableNode*> *listPtr);
    /*Gaseste pozitia str in lista list*/
    int findPos(const QString& str, const QStringList &list);
    /*Lista cu parametri declarati din declaratia de functie curenta*/
    QList<VariableNode*> *parameters;
    /*Lista cu numele parametrilor declarati din declaratia de functie curenta*/
    QStringList parametersNames;

    ExpressionTreeNode *currentNode;
%}

%union{
    ExpressionTreeNode* node;
    QList<ExpressionTreeNode*> *expr_seq;
    QList<VariableNode*> *param_seq;
    const char* ident;
    double val;
};

%start begin

%token IDENTIFIER NUMBER
 
%type <node> expresie
%type <node> expresie_if
%type <node> expresie_else
%type <node> expresie_algebrica
%type <node> expresie_relationala
%type <node> expresie_logica
%type <expr_seq> secventa_expresii
%type <param_seq> lista_parametri;
%type <val> NUMBER
%type <ident> IDENTIFIER

%token DOT COMMA RPAREN LPAREN LFUNCALL RFUNCALL
%nonassoc ASSIGN
%left ELSE
%nonassoc IF
%left AND OR
%nonassoc EQ NE GE GT LE LT
%left ADD MINUS
%left MUL DIV
%left NEG
%right POW

%%

begin : declaratie_constanta
    | declaratie_functie
;

declaratie_constanta : IDENTIFIER ASSIGN NUMBER DOT             {
                                                                    ConstantsTable::add(QString($1),$3);
                                                                }
;

declaratie_functie : IDENTIFIER LFUNCALL lista_parametri RFUNCALL ASSIGN expresie DOT {
                                                                                        parameters = $3;
                                                                                        int count = $3->count();
                                                                                        QString signature = QString($1)+"_"+QString::number(count);
                                                                                        ExpressionTreeNode* root = $6;
                                                                                        if(!root)
                                                                                            yyerror("Eroare:arborele sintactic nu a putut fi generat");

                                                                                        if(!UserFunTable::add(signature, parametersNames, root))
                                                                                        {
                                                                                            delete root;
                                                                                            yyerror("Functia nu poate fi redefinita");
                                                                                        }
                                                                                        clear(parameters);
                                                                                        parametersNames.clear();
                                                                                      }
;

lista_parametri : IDENTIFIER                                       {
                                                                        parametersNames.clear();
                                                                        $$ = new QList<VariableNode* >();
                                                                        $$->append(new VariableNode($1,0,0));
                                                                        parametersNames.append(QString($1));
                                                                   }
    | lista_parametri COMMA IDENTIFIER                             {
                                                                        int pos = $1->count();
                                                                        $1->append(new VariableNode($3,pos,0));
                                                                        parametersNames.append(QString($3));
                                                                        $$ = $1;
                                                                   }
;

expresie : expresie_else                                            {
                                                                        $$ = $1;
                                                                    }
    | expresie_if                                                   {
                                                                        $$ = $1;
                                                                    }
    | expresie_algebrica                                            {
                                                                        $$ = $1;
                                                                    }
;

expresie_else : expresie_if ELSE expresie_algebrica                 {
                                                                        $$ = BinaryOpNode::makeNew(";");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_if ELSE expresie_if                                  {
                                                                        $$ = BinaryOpNode::makeNew(";");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_if ELSE expresie_else                                {
                                                                        $$ = BinaryOpNode::makeNew(";");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | LPAREN expresie_else RPAREN                                   {
                                                                        $$ = $2;
                                                                    }
;

expresie_if : expresie_algebrica IF expresie_logica                 {
                                                                        $$ = BinaryOpNode::makeNew(":");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_algebrica IF expresie_relationala                    {
                                                                        $$ = BinaryOpNode::makeNew(":");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | LPAREN expresie_if RPAREN                                     {
                                                                         $$ = $2;
                                                                    }
;

expresie_logica : expresie_relationala OR expresie_relationala     {
                                                                        $$ = BinaryOpNode::makeNew("|");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_relationala AND expresie_relationala                 {
                                                                        $$ = BinaryOpNode::makeNew("&");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_logica OR expresie_relationala                       {
                                                                        $$ = BinaryOpNode::makeNew("|");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_logica AND expresie_relationala                      {
                                                                        $$ = BinaryOpNode::makeNew("&");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | LPAREN expresie_logica RPAREN                                 {
                                                                        $$ = $2;
                                                                    }
;

expresie_relationala : expresie_algebrica EQ expresie_algebrica     {
                                                                        $$ = BinaryOpNode::makeNew("==");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_algebrica NE expresie_algebrica                      {
                                                                        $$ = BinaryOpNode::makeNew("!=");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_algebrica LT expresie_algebrica                      {
                                                                        $$ = BinaryOpNode::makeNew("<");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_algebrica LE expresie_algebrica                      {
                                                                        $$ = BinaryOpNode::makeNew("<=");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_algebrica GT expresie_algebrica                      {
                                                                        $$ = BinaryOpNode::makeNew(">");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | expresie_algebrica GE expresie_algebrica                      {
                                                                        $$ = BinaryOpNode::makeNew(">");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                    }
    | LPAREN expresie_relationala RPAREN                            {
                                                                        $$ = $2;
                                                                    }
;

expresie_algebrica :  NUMBER                                        {
                                                                        $$ = new ValueNode($1);
                                                                    }

    | IDENTIFIER                                                    {
                                                                        QString str($1);
                                                                        int pos = findPos(str,parametersNames);
                                                                        if(pos>-1)
                                                                        {
                                                                            $$ = new VariableNode(str,pos);
                                                                        }
                                                                        else
                                                                        {
                                                                            Real val = ConstantsTable::find(str);
                                                                            if(!val.isNan())
                                                                            {
                                                                                $$ = new MathConstantNode(str);
                                                                            }
                                                                            else
                                                                            {
                                                                                yyerror("Parametru nedeclarat");
                                                                            }
                                                                        }
                                                                    }
    | MINUS expresie_algebrica %prec NEG                            {
                                                                        $$ = new UnaryOpNode("-");
                                                                        $$->setRight($2);
                                                                   }
    | expresie_algebrica ADD expresie_algebrica                    {
                                                                        $$ = BinaryOpNode::makeNew("+");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                   }
    | expresie_algebrica MINUS expresie_algebrica                  {
                                                                        $$ = BinaryOpNode::makeNew("-");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                   }
    | expresie_algebrica MUL expresie_algebrica                    {
                                                                        $$ = BinaryOpNode::makeNew("*");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                   }
    | expresie_algebrica DIV expresie_algebrica                    {
                                                                        $$ = BinaryOpNode::makeNew("/");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                   }
    | expresie_algebrica POW expresie_algebrica                    {
                                                                        $$ = BinaryOpNode::makeNew("^");
                                                                        $$->setLeft($1);
                                                                        $$->setRight($3);
                                                                   }
    | IDENTIFIER LFUNCALL secventa_expresii RFUNCALL               {
                                                                        int count = $3->count();
                                                                        QString signature = QString($1)+"_"+QString::number(count);
                                                                        ExpressionTreeNode* node = 0;
                                                                        if(count==1){
                                                                            if(FunTable::contains(signature)){
                                                                                node = new UnaryOpNode($1);
                                                                                node->setRight(new VariableNode("x",0,0));
                                                                             }
                                                                        }
                                                                        if(!node)
                                                                        {
                                                                            node = UserFunTable::find(signature);
                                                                        }
                                                                        if(!node)
                                                                        {
                                                                            yyerror("Functie nedeclarata");
                                                                        }
                                                                        else
                                                                        {
                                                                            QList<ExpressionTreeNode*>::iterator it = $3->begin();
                                                                            int pos = 0;
                                                                            ExpressionTreeNode* tmpNode;
                                                                            while(it!=$3->end()){
                                                                                tmpNode = node->replace(pos,*it);
                                                                                delete node;
                                                                                node = tmpNode;
                                                                                delete *it;
                                                                                ++pos;
                                                                                ++it;
                                                                            }
                                                                            $3->clear();
                                                                        }
                                                                        $$ = node;
                                                                   }
    | LPAREN expresie_algebrica RPAREN                             {
                                                                        $$ = $2;
                                                                   }
;

secventa_expresii : expresie                                       {
                                                                        $$ = new QList<ExpressionTreeNode*>;
                                                                        $$->append($1);
                                                                   }
    | secventa_expresii COMMA expresie                             {
                                                                        $1->append($3);
                                                                        $$ = $1;
                                                                   }
;
%%

void clear(QList<VariableNode*> *listPtr){
    if(listPtr){
        QList<VariableNode*>::iterator it;
        for(it=listPtr->begin();it!=listPtr->end();++it){
            delete *it;
        }
        listPtr->clear();
        delete listPtr;
        listPtr = 0;
    }
}

int findPos(const QString& str, const QStringList& list){
    for(int i = 0; i < list.count();++i){
        if(list[i]==str)
            return i;
    }
    return -1;
}
void yyerror(char const* msg){
    qWarning() << QString(msg);
}
